package com.adee.symmetry;

import com.adee.util.JDK;
import com.adee.util.Util;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/*
* 算法中使用int类型表示一个bit，取值0/1
    1)、输入64位明文数据，并进行初始置换IP；
    2)、在初始置换IP后，明文数据再被分为左右两部分，每部分32位，以L0，R0表示；
    3)、在秘钥的控制下，经过16轮运算(f)；
    4)、16轮后，左、右两部分交换，并连接再一起，再进行逆置换；
    5)、输出64位密文。
* */
public class DES1 {
    public static void main(String[] args) {
        String str = "yui763efgj74wscho871qzxcr68";
        String key = "1qaz@WSX";
        byte[] originBytes = str.getBytes(StandardCharsets.UTF_8);
        DES1 des = new DES1(originBytes, key);
        System.out.println("encrypt===========");
        byte[] encrypt = des.encrypt(originBytes);
        System.out.println("decrypt===========");
        byte[] decrypt = des.decrypt(encrypt);
        System.out.println("decrypt origin bytes:");
        Util.printByteArr(decrypt);
    }
    private Mode mode;
    // 原文
    private String plainText;
    // 密码字符串
    private String key;
    // 64位密钥
    private int[] K = new int[64];
    // 子密钥  64位秘钥产生16轮的48位子秘钥
    private final int[][] Ks = new int[16][48];
    // 子密钥  将Ks倒序，用于解密
    private final int[][] Ks_1 = new int[16][48];
    // 明文字节数组
    private byte[] originBytes;
    // 明文位分组指针，指向当前分组第一个字节下标，每分组64位8字节
    private int pointer = 0;
    // plainText 明文，key 密钥
    public DES1(byte[] originBytes, String key) {
        this.key = key;
        this.originBytes = originBytes;
        this.genK64();
        this.initKs();
    }
    // 对原始字节加密，返回密文
    byte[] encrypt(byte[] originBytes) {
        mode = Mode.ENCRYPT;
        pointer = 0;
        return doCrypt(originBytes, this.Ks);
    }
    // 解密
    byte[] decrypt(byte[] encryptBytes) {
        mode = Mode.DECRYPT;
        pointer = 0;
        byte[] paddedBytes = doCrypt(encryptBytes, this.Ks_1);
        int padLen = paddedBytes[paddedBytes.length-1] & 0x0f;
        byte[] ret = new byte[paddedBytes.length-padLen];
        System.arraycopy(paddedBytes, 0, ret, 0, ret.length);
        return ret;
    }
    // 加密/解密公共代码
    byte[] doCrypt(byte[] paramsBytes, int[][] Ks) {
        System.out.println("paramsBytes:");
        Util.printByteArr(paramsBytes);
        byte[] encrypt;
        if(mode == Mode.ENCRYPT) {
            encrypt = new byte[paramsBytes.length + (8 - paramsBytes.length % 8)];
        } else {
            encrypt = new byte[paramsBytes.length];
        }
        int[] group;
        int i = 0;
        while((group = nextGroup(paramsBytes)) != null) { // 每组加密
            group = IP(group); // 初始置换
            int[] c = round(group, Ks); // 64位8字节
            c = IP_1(c); // 逆初始置换
            for(int j = 0; j < 8; j++) { // 将加密后int[64]转为8字节
                byte bt = 0;
                for(int k = 0; k < 8; k++) {
                    bt |= c[j*8+k]<<k;
                }
                encrypt[i*8+j] = bt;
            }
            i++;
        }
        pointer = 0;
        System.out.println("encrypt/decrypt:");
        Util.printByteArr(encrypt);
        return encrypt;
    }
    // 密码处理为64位密钥
    void genK64() {
        byte[] md5 = JDK.md5(key.getBytes(StandardCharsets.UTF_8)); // 16字节
        for(int i = 0; i < 8; i++) { // 后8字节和前8字节相与
            md5[i] &= md5[i+8];
        }
        // 相与后的8字节，64位，就是初始密钥，转为int数组
        for(int i = 0; i < 8; i++) {
            for(int j = 0; j < 8; j++) {
                K[i*8+j] = (md5[i] & (0x1 << j)) >>> j;
            }
        }
        // 打印测试
        System.out.println("key byte64:");
        Util.printByteArrBinary(md5);
        System.out.println("key int[]:");
        System.out.println(Arrays.toString(K));
    }
    /* 初始化子密钥Ks和Ks_1：
    *   1. 64位初始密钥P进行置换选择1，得到56位输出K；
    *   2. 将K循环左移一定位数，再进行置换选择2得到48位输出k1(即第一轮的密钥)；
    *   3. 循环步骤2)，直至得到16轮的密钥（k1、k2、……、k16）；
    * */
    void initKs() {
        // 1. PC-1置换
        int[] pc1 = new int[56];
        for(int i = 0; i < 56; i++) {
            pc1[i] = K[PC1[i]-1];
        }
        //
        int[] C0 = new int[28];
        int[] D0 = new int[28];
        System.arraycopy(pc1, 0, C0, 0, 28);
        System.arraycopy(pc1, 28, D0, 0, 28);
        int[] Ci = C0;
        int[] Di = D0;
        int[] tmp = new int[56];
        for(int i = 0; i < 16; i++) {
            Ci = rotateLeft(Ci, LFT[i]);
            Di = rotateLeft(Di, LFT[i]);
            System.arraycopy(Ci, 0, tmp, 0, 28);
            System.arraycopy(Di, 0, tmp, 28, 28);
            // PC-2置换
            for(int j = 0; j < 48; j++) {
                Ks[i][j] = tmp[PC2[j]-1];
                Ks_1[15-i][j] = tmp[PC2[j]-1];
            }
        }
    }
    // 获取分组
    int[] nextGroup(byte[] paramBytes) {
        int[] ret = new int[64];
        if(mode == Mode.ENCRYPT) {
            if(pointer > paramBytes.length) return null;
            if(pointer+8 > paramBytes.length) { // 不够8字节，填充
                int padLen = pointer + 8 - paramBytes.length;
                for(int i = 0; i < 8-padLen; i++) {
                    byte b = paramBytes[pointer+i];
                    for(int j = 0; j < 8; j++) {
                        ret[i*8+j] = (b & (0x1<<j)) >>> j;
                    }
                }
                for(int i = 8-padLen; i < 8; i++) { // 填充padLen个padLen
                    for(int j = 0; j < 8; j++) {
                        ret[i*8+j] = ((byte)padLen & (0x1<<j)) >>> j;
                    }
                }
            }else { // 够8字节，则将8字节的位按顺序转为64元素的int数组
                for(int i = 0; i < 8; i++) {
                    for(int j = 0; j < 8; j++) {
                        ret[i*8+j] = (paramBytes[pointer+i] & (0x1<<j)) >>> j;
                    }
                }
            }
        } else {
            if(pointer >= paramBytes.length) return null;
            for(int i = 0; i < 8; i++) {
                for(int j = 0; j < 8; j++) {
                    ret[i*8+j] = (paramBytes[pointer+i] & (0x1<<j)) >>> j;
                }
            }
        }
        pointer += 8;
        return ret;
    }
    // 数组arr元素循环左移step
    static int[] rotateLeft(int[] arr, int step) {
        if(arr == null || arr.length == 0 || step < 1 || step % arr.length == 0) {
            System.err.println("static int[] rotateLeft(int[] arr, int step) params error");
            return arr;
        }
        int s = step % arr.length;
        int[] ret = new int[arr.length];
        System.arraycopy(arr, 0, ret, arr.length-step, step);
        System.arraycopy(arr, step, ret, 0, arr.length-step);
        return ret;
    }
    // 初始置换 64 -> 64
    static int[] IP(int[] input) {
        int[] ret = new int[64];
        for(int i = 0; i < 64; i++) {
            ret[i] = input[IP[i]-1];
        }
        return ret;
    }
    // 逆初始置换 64 -> 64
    static int[] IP_1(int[] input) {
        int[] ret = new int[64];
        for(int i = 0; i < 64; i++) {
            ret[i] = input[IP_1[i]-1];
        }
        return ret;
    }
    /*
        Feistel结构中的F函数，分三步：
        1.E扩展/置换，将32位输入扩展成48位输出；
        2.S盒：将48位输入分成8组分别作为8个S盒的输入，每组6位，最高位和最低位作为行索引，中间4位作为列索引，
          得到一个值x（0<=x<=15），表示为二进制即为4位，8个S盒的输出和在一起正好32位。表4是S1的数值；
        3.P置换：对32位输入进行位的变化，输出仍为32位。
    */
    static int[] F(int[] Ri /* 长度32 */, int[] Ki /* 长度48 */) {
        // E置换
        int[] e = new int[48];
        for(int i = 0; i < 48; i++) {
            e[i] = Ri[E[i]-1];
        }
        // 异或 e ^ Ki
        for(int i = 0; i < 48; i++) {
            e[i] = e[i] ^ Ki[i];
        }
        // S盒
        int[] s = new int[32]; // 结果
        int[] tmp = new int[4];
        for(int i = 0; i < 8; i++) { // e分为8组，每组6位（即6个元素）
            int row = (e[i] << 1) & e[i+5];
            int col = (e[i+1] << 3) & (e[i+2] << 2) & (e[i+3] << 1) & e[i+4];
            int x = S_Box[i][row][col]; // 4位的x 0<=x<=15
            tmp[0] = x & 0b1;
            tmp[1] = x & 0b10;
            tmp[2] = x & 0x100;
            tmp[3] = x & 0x1000;
            System.arraycopy(tmp, 0, s, 0, 4);
        }
        // P置换，32 -> 32
        int[] p = new int[32];
        for(int i = 0; i < 32; i++) {
            p[i] = s[P[i]-1];
        }
        return p;
    }

    /*
        Feistel 每轮操作，加密/解密共用
    */
    int[] round(int[] group, int[][] Ks) {
        int[] L0 = Arrays.copyOfRange(group, 0, 32);
        int[] R0 = Arrays.copyOfRange(group, 32, 64);
        int[] Li = L0;
        int[] Ri = R0;
        for(int i = 0; i < 15; i++) {
            int[] tmp = Li;
            Li = Ri;
            Ri = xor(tmp, F(Ri, Ks[i]));
        }
        // 最后一轮处理逻辑不同，单独处理，Li如下，Ri不变
        Li = xor(Li, F(Ri, Ks[15]));
        return merge(Li, Ri);
    }
    // 两个数组连接为一个数组
    static int[] merge(int[] a, int[] b) {
        if(a == null || b == null) return null;
        int len = a.length + b.length;
        int[] ret = new int[len];
        System.arraycopy(a, 0, ret, 0, a.length);
        System.arraycopy(b, 0, ret, a.length, b.length);
        return ret;
    }
    // 两个数组对应位置元素异或
    static int[] xor(int[] a, int[] b) {
        if(a == null || b == null || a.length != b.length) {
            System.err.println("int[] xor(int[] a, int[] b) params error");
            return null;
        }
        int[] ret = new int[a.length];
        for(int i = 0; i < ret.length; i++) {
            ret[i] = a[i] ^ b[i];
        }
        return ret;
    }



    /*************************************************************************/
    //初始置换
    private static int[] IP={58,50,42,34,26,18,10,2,
            60,52,44,36,28,20,12,4,
            62,54,46,38,30,22,14,6,
            64,56,48,40,32,24,16,8,
            57,49,41,33,25,17,9,1,
            59,51,43,35,27,19,11,3,
            61,53,45,37,29,21,13,5,
            63,55,47,39,31,23,15,7};
    //逆初始置换
    private static int[] IP_1={40,8,48,16,56,24,64,32,
            39,7,47,15,55,23,63,31,
            38,6,46,14,54,22,62,30,
            37,5,45,13,53,21,61,29,
            36,4,44,12,52,20,60,28,
            35,3,43,11,51,19,59,27,
            34,2,42,10,50,18,58,26,
            33,1,41,9,49,17,57,25};
    //E扩展
    private static int[] E={32,1,2,3,4,5,
            4,5,6,7,8,9,
            8,9,10,11,12,13,
            12,13,14,15,16,17,
            16,17,18,19,20,21,
            20,21,22,23,24,25,
            24,25,26,27,28,29,
            28,29,30,31,32,1};
    private static final int[][][] S_Box = {
            {
                    {14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7},
                    {0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8},
                    {4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0},
                    {15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13}},
            {
                    {15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10},
                    {3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5},
                    {0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15},
                    {13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9}},
            {
                    {10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8},
                    {13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1},
                    {13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7},
                    {1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12}},
            {
                    {7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15},
                    {13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9},
                    {10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4},
                    {3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14}},
            {
                    {2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9},
                    {14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6},
                    {4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14},
                    {11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3}},
            {
                    {12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11},
                    {10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8},
                    {9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6},
                    {4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13}},
            {
                    {4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1},
                    {13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6},
                    {1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2},
                    {6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12}},
            {
                    {13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7},
                    {1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2},
                    {7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8},
                    {2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}}
    };
    //P置换
    private static int[] P = {16,7,20,21,29,12,28,17,
            1,15,23,26,5,18,31,10,
            2,8,24,14,32,27,3,9,
            19,13,30,6,22,11,4,25};
    //PC-1
    private static int[] PC1={57,49,41,33,25,17,9,
            1,58,50,42,34,26,18,
            10,2,59,51,43,35,27,
            19,11,3,60,52,44,36,
            63,55,47,39,31,23,15,
            7,62,54,46,38,30,22,
            14,6,61,53,45,37,29,
            21,13,5,28,20,12,4};
    //PC-2
    private static int[] PC2={14,17,11,24,1,5,3,28,
            15,6,21,10,23,19,12,4,
            26,8,16,7,27,20,13,2,
            41,52,31,37,47,55,30,40,
            51,45,33,48,44,49,39,56,
            34,53,46,42,50,36,29,32};

    //Schedule of Left Shifts
    private static int[] LFT={1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1};
}

enum Mode {
    ENCRYPT,
    DECRYPT,
}
